{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Named Tuples"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``namedtuple`` function in ``collections`` allows us to create a tuple that also has names attached to each field (aka property). This can be handy to reference data in the tuple structure by name instead of just relying on position.\n",
    "\n",
    "The ``namedtuple`` function is basically a class factory that creates a new type of class that uses a tuple as its underlying data storage (in fact, named tuples inherit from `tuple`), but layers in field names to each position and makes a property out of the field name.\n",
    "\n",
    "The ``namedtuple`` function creates a **class**, and we then use that class to instantiate our instances of named tuples."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To use the ``namedtuple`` function we therefore need to select a class **name**, as well as indicate the **property** names, in the order in which they will be stored and accessed in the tuple.\n",
    "\n",
    "Note that a ``namedtuple``, like the regular ``tuple`` is an **immutable** data structure. (In fact, named tuples inherit from tuples - we'll revisit this in our section on metaclasses)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you find yourself writing code such as:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Point3D:\n",
    "    def __init__(self, x, y, z):\n",
    "        self.x = x\n",
    "        self.y = y\n",
    "        self.z = z"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Forget it! You seriously need to use named tuples! Not only can you shorten the amount of code you need to write, but you get some additional functionality for \"free\", such as `__repr__` and `__eq__` that you do not have to implement yourself!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Creating Named Tuples"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We are going to create a ``Point`` named tuple that will contain an x-coordinate and a y-coordinate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from collections import namedtuple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "Point2D = namedtuple('Point2D', ('x', 'y'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that we have two different uses of `Point2D` here. The label we are assigning the return value of the call to ``namedtuple`` and the **name** of the class generated by calling ``namedtuple``.\n",
    "\n",
    "We could also have done the following:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "Pt = namedtuple('Point2D', ('x', 'y'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``namedtuple`` class name is `Point2D`, but the label we `Pt` simply points to that class, so we would then create instances of the `Point2D` class as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "pt1 = Pt(10, 20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can see what `pt1` is:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point2D(x=10, y=20)"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see we have an object of type `Point2D`, and it has two properties, `x` and `y` with respective values `10` and `20`.\n",
    "\n",
    "The only weird thing here is that we are using `Pt` to generate our instances of the `Point2D` class.\n",
    "\n",
    "That's why we usually always created `namedtuple` generated classes this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "Point2D = namedtuple('Point2D', ('x', 'y'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then the following makes more sense:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "pt1 = Point2D(10, 20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point2D(x=10, y=20)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is not different than doing this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "Pt3 = Point3D  # class we defined earlier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "pt3 = Pt3(10, 20, 30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Point3D at 0x27408e1fa90>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see above, we used another label `Pt3` as a label that also references the `Point3D` class. It would be weird to do it this way here, and its weird for tuples as well. Of course, you may run into circumstances where you need to do this - just not as a general rule."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that all named tuples  are honest to goodness **classes**, just as if you had used a `class` definition such as with `Point3D`. \n",
    "\n",
    "The `namedtuple` function **generates** classes for us - it is a **class factory**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "type"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(Point3D)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "type"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(Point2D)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, `Point2D` is a subclass of `tuple`, while `Point3D` is not:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "isinstance(pt1, tuple)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "isinstance(pt3, tuple)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, when we create an instance of a class, we are in fact calling the `__new__` method with our initial values. It's just a callable that has the **field names** we used to generate our named tuple class as its parameters. This means we can use keyword arguments when instantiating our named tuples!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "pt4 = Point2D(y=20, x=10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point2D(x=10, y=20)"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt4"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### What did we get for free using a named tuple vs our own class?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First using a named tuple for our 2D point:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "pt2d_1 = Point2D(10, 20)\n",
    "pt2d_2 = Point2D(10, 20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point2D(x=10, y=20)"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt2d_1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt2d_1 == pt2d_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now using our 3D class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "pt3d_1 = Point3D(10, 20, 30)\n",
    "pt3d_2 = Point3D(10, 20, 30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Point3D at 0x27408e1f9e8>"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt3d_1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Oh, we probably need to implement the `__repr__` method in our class"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt3d_1 == pt3d_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we would also need to implement the __eq__ method!\n",
    "\n",
    "Let's do that:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Point3D:\n",
    "    def __init__(self, x, y, z):\n",
    "        self.x = x\n",
    "        self.y = y\n",
    "        self.z = z\n",
    "    \n",
    "    def __repr__(self):\n",
    "        return f\"Point3D(x={self.x}, y={self.y}, z={self.z})\"\n",
    "    \n",
    "    def __eq__(self, other):\n",
    "        if isinstance(other, Point3D):\n",
    "            return self.x == other.x and self.y == other.y and self.z == other.z\n",
    "        else:\n",
    "            return False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "pt3d_1 = Point3D(10, 20, 30)\n",
    "pt3d_2 = Point3D(10, 20, 30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point3D(x=10, y=20, z=30)"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt3d_1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt3d_1 == pt3d_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "How about finding the largest coordinate in the point?\n",
    "\n",
    "That's easy for `Point2D` since it is a tuple, but not the case for `Point3D`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "20"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "max(pt2d_1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "'Point3D' object is not iterable",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-30-e803e2758ff1>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mmax\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mpt3d_1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m: 'Point3D' object is not iterable"
     ]
    }
   ],
   "source": [
    "max(pt3d_1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "How about calculating the dot product of two points (considering them as vectors starting at the origin)?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The formula would be:\n",
    "a.b = a.x * b.x + a.y + b.y + a.z * b.z"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For the 3D point we would need to do the following:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def dot_product_3d(a, b):\n",
    "    return a.x * b.x + a.y * b.y + a.z + b.z"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "560"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dot_product_3d(pt3d_1, pt3d_2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But for our 2D point, which, remember is a tuple as well, we can write a generic function that would work equally well with a 3D named tuple too:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def dot_product(a, b):\n",
    "    return sum(e[0] * e[1] for e in  zip(a, b))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's a break down of how we implemented the dot product:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First we zip up the components of `a` and `b` to get an iterable of tuples containing the x-coordinates in the 1st element, and the y-coordinates in the second tuple. Our zip will contain as many elements as there are dimensions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Point2D(x=1, y=2)\n",
      "Point2D(x=10, y=20)\n",
      "(1, 2)\n",
      "(10, 20)\n",
      "[(1, 10), (2, 20)]\n"
     ]
    }
   ],
   "source": [
    "a = Point2D(1, 2)\n",
    "b = Point2D(10, 20)\n",
    "print(a)\n",
    "print(b)\n",
    "print(tuple(a))\n",
    "print(tuple(b))\n",
    "print(list(zip(a, b)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that if we had more dimensions this would work equally well.\n",
    "\n",
    "Suppose we had 3 dimensions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(1, 10), (2, 20), (3, 30)]"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "u = (1, 2, 3)\n",
    "v = (10, 20, 30)\n",
    "list(zip(u, v))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we create a comprehension that multiplies the components together:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[10, 40]"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[e[0] * e[1] for e in zip(a, b)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we simply add those up:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sum([e[0] * e[1] for e in zip(a, b)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dot_product(a, b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And if we defined a 4D point named tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "Point4D = namedtuple('Point4D', ['i', 'j', 'k', 'l'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "pt4d_1 = (1, 1, 1, 10)\n",
    "pt4d_2 = (2, 2, 2, 10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "106"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dot_product(pt4d_1, pt4d_2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see we got the correct dot product. We could not have done this using our `Point3D` class!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Other Ways to Specify Field Names"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are a number of ways we can specify the field names for the named tuple:\n",
    "\n",
    "* we can provide a sequence of strings containing each property name\n",
    "* we can provide a single string with property names separated by whitespace or a comma"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "Circle = namedtuple('Circle', ['center_x', 'center_y', 'radius'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "circle_1 = Circle(0, 0, 10)\n",
    "circle_2 = Circle(center_x=10, center_y=20, radius=100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Circle(center_x=0, center_y=0, radius=10)"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "circle_1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Circle(center_x=10, center_y=20, radius=100)"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "circle_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Or we can do it this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "City = namedtuple('City', 'name country population')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "new_york = City('New York', 'USA', 8_500_000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "City(name='New York', country='USA', population=8500000)"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "new_york"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This would work equally well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "Stock = namedtuple('Stock', 'symbol, year, month, day, open, high, low, close')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "djia = Stock('DJIA', 2018, 1, 25, 26_313, 26_458, 26_260, 26_393)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Stock(symbol='DJIA', year=2018, month=1, day=25, open=26313, high=26458, low=26260, close=26393)"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "djia"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In fact, since whitespace can be used we can even use a multi-line string!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "Stock = namedtuple('Stock', '''symbol\n",
    "                               year month day\n",
    "                               open high low close''')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "djia = Stock('DJIA', 2018, 1, 25, 26_313, 26_458, 26_260, 26_393)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Stock(symbol='DJIA', year=2018, month=1, day=25, open=26313, high=26458, low=26260, close=26393)"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "djia"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Accessing Items in a Named Tuple"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The major advantage of named tuples are that, as the name suggests, we can access the properties (fields) of the tuple by name:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point2D(x=10, y=20)"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt1.x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Circle(center_x=0, center_y=0, radius=10)"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "circle_1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "circle_1.radius"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now named tuples *are* tuples, so elements can be accessed by index, unpacked, and iterated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "circle_1[2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DJIA\n",
      "2018\n",
      "1\n",
      "25\n",
      "26313\n",
      "26458\n",
      "26260\n",
      "26393\n"
     ]
    }
   ],
   "source": [
    "for item in djia:\n",
    "    print(item)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also unpack named tuples just like ordinary tuples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Point2D(x=10, y=20)"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pt1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "x, y = pt1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10 20\n"
     ]
    }
   ],
   "source": [
    "print(x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also use extended unpacking:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Stock(symbol='DJIA', year=2018, month=1, day=25, open=26313, high=26458, low=26260, close=26393)"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "djia"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "symbol, *_, close = djia"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "DJIA 26393\n"
     ]
    }
   ],
   "source": [
    "print(symbol, close)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And remember that the `_` we use in the unpacking is just a regular variable:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2018, 1, 25, 26313, 26458, 26260]\n"
     ]
    }
   ],
   "source": [
    "print(_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The field names for these named tuples can be any valid variable name **except** that they cannot start with an underscore. \n",
    "\n",
    "For example the following would not be valid:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "Field names cannot start with an underscore: '_age'",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-68-cc651156ccc1>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mPerson\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mnamedtuple\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'Person'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;34m'firstname'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'lastname'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'_age'\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'ssn'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32mD:\\Users\\fbapt\\Anaconda3\\envs\\deepdive\\lib\\collections\\__init__.py\u001b[0m in \u001b[0;36mnamedtuple\u001b[1;34m(typename, field_names, verbose, rename, module)\u001b[0m\n\u001b[0;32m    409\u001b[0m         \u001b[1;32mif\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstartswith\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'_'\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mand\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mrename\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    410\u001b[0m             raise ValueError('Field names cannot start with an underscore: '\n\u001b[1;32m--> 411\u001b[1;33m                              '%r' % name)\n\u001b[0m\u001b[0;32m    412\u001b[0m         \u001b[1;32mif\u001b[0m \u001b[0mname\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mseen\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    413\u001b[0m             \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'Encountered duplicate field name: %r'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mValueError\u001b[0m: Field names cannot start with an underscore: '_age'"
     ]
    }
   ],
   "source": [
    "Person = namedtuple('Person', ['firstname', 'lastname', '_age', 'ssn'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also choose to let the `namedtuple` function replace invalid field names automatically for us, by using the keyword argument `rename`. When we set that argument to `True` (it is `False` by default) it will replace the invalid name using the position (index) of the field, preceded by an underscore:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "Person = namedtuple('Person', ['firstname', 'lastname', '_age', 'ssn'], rename=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "eric = Person('Eric', 'Idle', 42, 'unknown')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Person(firstname='Eric', lastname='Idle', _2=42, ssn='unknown')"
      ]
     },
     "execution_count": 71,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "eric"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see the invalid field name `_y` was replaced by `_1` since it was the second element (i.e. index of `1`)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Named Tuple Internals"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can easily find out the fields in a named tuple using the `_fields` property:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('x', 'y')"
      ]
     },
     "execution_count": 72,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Point2D._fields"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('symbol', 'year', 'month', 'day', 'open', 'high', 'low', 'close')"
      ]
     },
     "execution_count": 73,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Stock._fields"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There is also a property, `_source` that allows us to see exactly the class that was generated by calling `namedtuple` (remember that `namedtuple` is a class **factory**):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "from builtins import property as _property, tuple as _tuple\n",
      "from operator import itemgetter as _itemgetter\n",
      "from collections import OrderedDict\n",
      "\n",
      "class Point2D(tuple):\n",
      "    'Point2D(x, y)'\n",
      "\n",
      "    __slots__ = ()\n",
      "\n",
      "    _fields = ('x', 'y')\n",
      "\n",
      "    def __new__(_cls, x, y):\n",
      "        'Create new instance of Point2D(x, y)'\n",
      "        return _tuple.__new__(_cls, (x, y))\n",
      "\n",
      "    @classmethod\n",
      "    def _make(cls, iterable, new=tuple.__new__, len=len):\n",
      "        'Make a new Point2D object from a sequence or iterable'\n",
      "        result = new(cls, iterable)\n",
      "        if len(result) != 2:\n",
      "            raise TypeError('Expected 2 arguments, got %d' % len(result))\n",
      "        return result\n",
      "\n",
      "    def _replace(_self, **kwds):\n",
      "        'Return a new Point2D object replacing specified fields with new values'\n",
      "        result = _self._make(map(kwds.pop, ('x', 'y'), _self))\n",
      "        if kwds:\n",
      "            raise ValueError('Got unexpected field names: %r' % list(kwds))\n",
      "        return result\n",
      "\n",
      "    def __repr__(self):\n",
      "        'Return a nicely formatted representation string'\n",
      "        return self.__class__.__name__ + '(x=%r, y=%r)' % self\n",
      "\n",
      "    def _asdict(self):\n",
      "        'Return a new OrderedDict which maps field names to their values.'\n",
      "        return OrderedDict(zip(self._fields, self))\n",
      "\n",
      "    def __getnewargs__(self):\n",
      "        'Return self as a plain tuple.  Used by copy and pickle.'\n",
      "        return tuple(self)\n",
      "\n",
      "    x = _property(_itemgetter(0), doc='Alias for field number 0')\n",
      "\n",
      "    y = _property(_itemgetter(1), doc='Alias for field number 1')\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(Point2D._source)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And of course this will be slightly different for another named tuple generated class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "from builtins import property as _property, tuple as _tuple\n",
      "from operator import itemgetter as _itemgetter\n",
      "from collections import OrderedDict\n",
      "\n",
      "class Person(tuple):\n",
      "    'Person(firstname, lastname, _2, ssn)'\n",
      "\n",
      "    __slots__ = ()\n",
      "\n",
      "    _fields = ('firstname', 'lastname', '_2', 'ssn')\n",
      "\n",
      "    def __new__(_cls, firstname, lastname, _2, ssn):\n",
      "        'Create new instance of Person(firstname, lastname, _2, ssn)'\n",
      "        return _tuple.__new__(_cls, (firstname, lastname, _2, ssn))\n",
      "\n",
      "    @classmethod\n",
      "    def _make(cls, iterable, new=tuple.__new__, len=len):\n",
      "        'Make a new Person object from a sequence or iterable'\n",
      "        result = new(cls, iterable)\n",
      "        if len(result) != 4:\n",
      "            raise TypeError('Expected 4 arguments, got %d' % len(result))\n",
      "        return result\n",
      "\n",
      "    def _replace(_self, **kwds):\n",
      "        'Return a new Person object replacing specified fields with new values'\n",
      "        result = _self._make(map(kwds.pop, ('firstname', 'lastname', '_2', 'ssn'), _self))\n",
      "        if kwds:\n",
      "            raise ValueError('Got unexpected field names: %r' % list(kwds))\n",
      "        return result\n",
      "\n",
      "    def __repr__(self):\n",
      "        'Return a nicely formatted representation string'\n",
      "        return self.__class__.__name__ + '(firstname=%r, lastname=%r, _2=%r, ssn=%r)' % self\n",
      "\n",
      "    def _asdict(self):\n",
      "        'Return a new OrderedDict which maps field names to their values.'\n",
      "        return OrderedDict(zip(self._fields, self))\n",
      "\n",
      "    def __getnewargs__(self):\n",
      "        'Return self as a plain tuple.  Used by copy and pickle.'\n",
      "        return tuple(self)\n",
      "\n",
      "    firstname = _property(_itemgetter(0), doc='Alias for field number 0')\n",
      "\n",
      "    lastname = _property(_itemgetter(1), doc='Alias for field number 1')\n",
      "\n",
      "    _2 = _property(_itemgetter(2), doc='Alias for field number 2')\n",
      "\n",
      "    ssn = _property(_itemgetter(3), doc='Alias for field number 3')\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(Person._source)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Converting Named Tuples to Dictionaries"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `namedtuple` generated class also provides us an instance method, `_asdict()` that will create a dictionary from all the fields in the named tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "OrderedDict([('firstname', 'Eric'),\n",
       "             ('lastname', 'Idle'),\n",
       "             ('_2', 42),\n",
       "             ('ssn', 'unknown')])"
      ]
     },
     "execution_count": 76,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "eric._asdict()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Technically, it is an `OrderedDict` which we will cover in later section. Basically an `OrderedDict` is a dictionary that, unlike the standard built-in `Dictionary` is **guaranteed** to preserve the order of the keys.\n",
    "\n",
    "[**Note** that as of Python 3.6, regular dictionaries **do** preserve the order of the keys, but until just recently it was not **guaranteed** and was bascially an implementation detail.\n",
    "\n",
    "**However, this has now changed!!** Guido van Rossum has now agreed that this is no longer an implementation detail, and starting in Python 3.7 dictionary order is guaranteed. Since it is actually already the case in Python 3.6, you can now safely assume this fact - as long as you are running your code under Python 3.6 or higher. Your code will break if you rely on dictionary order prior to 3.6, in that case, still use an `OrderedDict`]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Overhead of Named Tuples"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point you may be wondering whether there's more overhead to using a named tuple vs a regular tuple.\n",
    "\n",
    "There is, but it is tiny. The field names are stored in the **class**, not every instance of the named tuples.\n",
    "This means that the overhead incurred by the field names for one instance of the named tuple vs 1000 instances is the same. Otherwise, the instances are tuples, so you can access contained objects using indexing, slicing and iteration just as if it were a plain tuple. No overhead there either. Looking up values by name do have some overhead of course, but no more than if you had created a custom class."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
